﻿using Microsoft.Extensions.Logging;
using MiniJob.Worker.Jobs;
using System.Buffers;
using System.Text.Json;

namespace MiniJob.Worker.Runtime;

public sealed class MiniJobRuntime
{
    private readonly MiniJobRuntimeOptions options;
    private readonly ILogger logger;
    private readonly JobActivatorFactory activatorFactory;
    private readonly Dictionary<string, JobManager> jobManagers = [];

    internal MiniJobRuntime(MiniJobRuntimeOptions options, ILoggerFactory loggerFactory, JobActivatorFactory activatorFactory)
    {
        this.options = options;
        this.logger = loggerFactory.CreateLogger(this.GetType());
        this.activatorFactory = activatorFactory;

        foreach (var job in options.Jobs)
        {
            this.jobManagers[job.Type.JobTypeName] = 
                new JobManager(job, loggerFactory, job.Activator ?? this.activatorFactory.CreateActivator(job.Type));
        }
    }

    public IReadOnlyList<JobRegistration> RegisteredJobs => this.options.Jobs;

    internal Task SerializeQueryStatus(IBufferWriter<byte> output, string instanceId)
    {
        using Utf8JsonWriter writer = new Utf8JsonWriter(output);
        writer.WriteStartObject();

        writer.WritePropertyName("instanceId");
        writer.WriteStringValue(instanceId);

        writer.WriteEndObject();
        return writer.FlushAsync();
    }

    internal Task SerializeRegisteredJobs(IBufferWriter<byte> output)
    {
        using Utf8JsonWriter writer = new(output);
        writer.WriteStartObject();

        writer.WritePropertyName("appName");
        writer.WriteStringValue(options.AppName);

        writer.WritePropertyName("entities");
        writer.WriteStartArray();

        foreach (var job in this.RegisteredJobs)
        {
            writer.WriteStringValue(job.Type.JobTypeName);
        }

        writer.WriteEndArray();

        writer.WriteEndObject();
        return writer.FlushAsync();
    }

    internal Task RunJobAsync(JobContext jobContext, CancellationToken cancellationToken = default)
    {
        using (this.logger.BeginScope("JobType: {JobTypeName}, JobId: {JobId}, InstanceId: {InstanceId}", jobContext.JobTypeName, jobContext.JobId, jobContext.InstanceId))
        {
            return GetJobManager(jobContext.JobTypeName).RunJobAsync(jobContext, cancellationToken);
        }
    }

    private JobManager GetJobManager(string jobTypeName)
    {
        if (!this.jobManagers.TryGetValue(jobTypeName, out var jobManager))
        {
            var errorMsg = $"Job type {jobTypeName} is not registered with Job runtime.";
            throw new InvalidOperationException(errorMsg);
        }

        return jobManager;
    }
}
